using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Xfinity.Maths
{
    /// <summary>
    /// Misc. math functions.
    /// </summary>
    public static class MathFunctions
    {
        #region Constants
        public const float OneOverRootTwoPi = 0.39894228040143267793994605993438f;
        #endregion
        #region Static variables
        private static Random random = new Random();
        #endregion
        /// <summary>
        /// Calculates a gaussian distribution.
        /// </summary>
        /// <param name="standardDeviation">The standard deviation.</param>
        /// <param name="expectedValue">The expected value.</param>
        /// <param name="x">The point to evaluate at.</param>
        /// <returns>The probability.</returns>
        public static float GaussianDistribution(float standardDeviation, float expectedValue, float x)
        {
            double deviation = OneOverRootTwoPi / standardDeviation;

            double xMinusValue = x - expectedValue;
            double xMVSquared = xMinusValue * xMinusValue;
            double twoDeviation = standardDeviation * 2;
            double twoDSquared = twoDeviation * twoDeviation;
            double power = xMVSquared / twoDSquared;
            return (float)(deviation * Math.Pow(MathHelper.E, power));
        }
        /// <summary>
        /// The Sigmoid function.
        /// </summary>
        /// <param name="x">The position to evaluate.</param>
        /// <param name="midpoint">The midpoint of the curve.</param>
        /// <returns>The height.</returns>
        public static float Sigmoid(float x, float midpoint, float steepness)
        {
            float adjusted = x - midpoint;
            float power = (float)Math.Pow(Math.Pow(MathHelper.E, adjusted), -steepness);
            return 1 / (1 + power);
        }
        /// <summary>
        /// Generates a random direction vector.
        /// </summary>
        /// <returns>A random direction.</returns>
        public static Vector3 RandomDirection()
        {
            // Generate the random values in polar space, otherwise there will be an uneven distribution.
            double rho, theta;
            rho = (random.NextDouble() - .5) * 2;
            theta = (random.NextDouble() - .5) * 2;
            double sinRho = Math.Sin(rho);

            float x = (float)(sinRho * Math.Cos(theta));
            float y = (float)(sinRho * Math.Sin(theta));
            float z = (float)(Math.Cos(rho));

            return new Vector3(x, y, z);
        }
        /// <summary>
        /// Evaluates an arbitrary Bezier curve. If there are three points, use a BezierCurve structure instead, as it's more efficient for three points.
        /// </summary>
        /// <param name="points">The control points.</param>
        /// <param name="t">The parameter.</param>
        /// <returns>The point at B(i).</returns>
        public static Vector3 BezierCurve(IList<Vector3> points, float t)
        {
            uint n = (uint)points.Count;
            float oneMinusT = 1 - t;
            Vector3 point;
            point = points[0] * Pow(oneMinusT, n);
            point += points.Count * points[1] * Pow(oneMinusT, n - 1) * t;
            for (uint i = 2; i < points.Count - 2; i++)
            {
                point += BinomialCoefficient(n, i) * points[(int)i] * Pow(oneMinusT, n - i) * Pow(t, i);
            }
            point += points[points.Count - 1] * Pow(t, n);
            return point;
        }
        /// <summary>
        /// Raises a value to an integer power.
        /// </summary>
        /// <param name="x">The value.</param>
        /// <param name="y">The power.</param>
        public static float Pow(float x, uint y)
        {
            float value = 1;
            for (uint i = 0; i < y; i++)
            {
                value *= x;
            }
            return value;
        }
        /// <summary>
        /// Computes the binomial coefficient of two numbers, also known as the choose function.
        /// </summary>
        /// <param name="n">The number of options.</param>
        /// <param name="k">The number of choices.</param>
        /// <returns></returns>
        public static ulong BinomialCoefficient(uint n, uint k)
        {
            uint value = k + 1; ;

            for (uint i = value; i <= n; i++)
            {
                value *= i;
            }

            ulong nMinusKBang = Factorial(n - k);
            return value / (nMinusKBang);
        }
        /// <summary>
        /// Computes the mathematical intersection of two sets.
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="a">The first set.</param>
        /// <param name="b">The second set.</param>
        /// <returns>The intersection.</returns>
        public static List<T> Intersection<T>(IList<T> a, IList<T> b)
        {
            List<T> set = new List<T>(Math.Min(a.Count, b.Count));
            foreach (T elementA in a)
            {
                if (b.Contains(elementA))
                {
                    set.Add(elementA);
                }
            }
            set.TrimExcess();
            return set;
        }
        /// <summary>
        /// Computes the mathematical union of two sets.
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="a">The first set.</param>
        /// <param name="b">The second set.</param>
        /// <returns>The union.</returns>
        public static List<T> Union<T>(IList<T> a, IList<T> b)
        {
            List<T> union = new List<T>(a.Count + b.Count);
            union.AddRange(a);
            foreach (T element in a)
            {
                if (!b.Contains(element))
                {
                    union.Add(element);
                }
            }
            union.TrimExcess();
            return union;
        }
        /// <summary>
        /// Computes the mathematical disjoint of two sets.
        /// </summary>
        /// <typeparam name="T">The type.</typeparam>
        /// <param name="a">The first set.</param>
        /// <param name="b">The second set.</param>
        /// <returns>The disjoint.</returns>
        public static List<T> Disjoint<T>(IList<T> a, IList<T> b)
        {
            List<T> set = new List<T>(b);
            foreach (T element in b)
            {
                if (set.Contains(element))
                {
                    set.Remove(element);
                }
                else
                {
                    set.Add(element);
                }
            }
            set.TrimExcess();
            return set;
        }
        /// <summary>
        /// Returns the factorial of a number.
        /// </summary>
        /// <param name="n">The number.</param>
        /// <returns>The computed factorial.</returns>
        public static ulong Factorial(uint n)
        {
            ulong factorial = 1;
            for (uint i = 2; i <= n; i++)
            {
                factorial *= i;
            }
            return factorial;
        }
    }
}